package com.ywk.springCore.aop.aspectLog;

import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 结合注解实现日志跟踪切面（Aop）
 * @author zbh
 * @date 2022/2/25 16:00
 */
@Component
@Aspect
@Slf4j
public class LogAspect {

    /**
     * 切入点
     */
    @Pointcut("@annotation(com.ywk.springCore.aop.aspectLog.ServiceLog) && execution(* com.ywk.springCore.aop.test.web.AopTest.aopApi(..))")
    public void entryPoint() {
        // 无需内容
    }

    /**
     * 切入点
     */
    @Pointcut("@annotation(com.ywk.springCore.aop.aspectLog.ServiceLog)")
    public void entryPointV1() {
        // 无需内容
    }

    /**
     * 前置通知：方法执行前处理
     * @param joinPoint
     */
    @Before("entryPoint()")
    public void before(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 打印请求相关参数
        log.info("========================================== Start ==========================================");
        // 打印请求 url
        log.info("URL : {}", request.getRequestURL().toString());
        // 打印 Http method
        log.info("HTTP Method  : {}", request.getMethod());
        // 打印调用 controller 的全路径以及执行方法
        log.info("Class Method  : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
        // 打印请求的 IP
        log.info("IP : {}", request.getRemoteAddr());
        // 打印请求入参
        String args = new Gson().toJson(joinPoint.getArgs());
        log.info("Request Args  : {}", args);
    }

    /**
     * 后置通知：方法执行后处理，通常用于资源释放
     * @param joinPoint
     */
    @After("entryPoint()")
    public void after (JoinPoint joinPoint){

    }

    /**
     * 在方法正常执行通过之后执行的通知叫做返回通知
     * 可以返回到方法的返回值 在注解后加入returning
     * @param jp
     * @param result
     */
    @AfterReturning(value="entryPoint()",returning="result")
    public void doAfterReturning(JoinPoint jp,Object result){
        // 打印出参
        log.info("Response Args : {}", new Gson().toJson(result));
        log.info("=========================================== End ===========================================");
        // 每个请求之间空一行
        log.info("");
    }

    /**
     * 环绕通知处理：近似等于Before增强处理和AfterReturning增强处理的总和
     * Around增强处理可以改变执行目标方法的参数值，也可以改变执行目标方法之后的返回值，
     * 但通常需要在线程安全的环境下使用，所以一般用Before和AfterReturning增强处理能解决的问题，不建议用Around
     * @param point
     * @throws Throwable
     */
    @Async
    @Around("entryPoint() || entryPointV1()")
    public Object around (ProceedingJoinPoint point) throws Throwable {
        log.info("前置通知前");
        long startTime = System.currentTimeMillis();
        //执行切入点方法
        Object result = point.proceed();
        handleAround(point);
        // 执行耗时
        log.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);
        // 打印出参
//        log.info("Response Args : {}", new Gson().toJson(result));
        return result;
    }

    /**
     * around日志记录
     *
     * @param point
     * @throws SecurityException
     * @throws NoSuchMethodException
     */
    public void handleAround (ProceedingJoinPoint point) throws Exception {
        Signature sig = point.getSignature();
        MethodSignature msig ;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        msig = (MethodSignature) sig;
        Object target = point.getTarget();
        Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        // 方法名称
        String methodName = currentMethod.getName();
        // 获取注解对象
        ServiceLog aLog = currentMethod.getAnnotation(ServiceLog.class);
        // 类名
        String className = point.getTarget().getClass().getName();
        // 方法的参数
        Object[] params = point.getArgs();

        StringBuilder paramsBuf = new StringBuilder();
        for (Object arg : params) {
            paramsBuf.append(arg);
            paramsBuf.append("&");
        }
        // 处理log。。。。
        System.out.println("[X用户]执行了[" + aLog.operation() + "],类:" + className + ",方法名：" + methodName + ",参数:"
                + paramsBuf.toString());

    }
}
